package com.zy.explore.config;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.zy.explore.constant.MessageCode;
import com.zy.explore.constant.StatusCode;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * description Swagger2自动配置器
 *
 * @author ZY
 * @date 2021-05-05 17:53
 */
@Configuration
@ConditionalOnClass({ Docket.class, Swagger2DocumentationConfiguration.class })
@ConditionalOnProperty(value = SwaggerProperties.ENABLED, matchIfMissing = true)
@ConditionalOnWebApplication
@ConditionalOnBean(Swagger2DocumentationConfiguration.class)
@Import(value = { SwaggerProperties.class })
@Profile(value = { "dev", "test" })
public class SwaggerConfigurer {

    private static final String ALL_MATCH_PATH = "/**";

    private final ConfigurableBeanFactory beanFactory;

    /**
     * 加载swagger2配置文件
     */
    private final SwaggerProperties properties;
    
    public SwaggerConfigurer(ConfigurableBeanFactory beanFactory, SwaggerProperties properties) {
        this.beanFactory = beanFactory;
        this.properties = properties;
    }

    @PostConstruct
    public void createRestApi() {
        List<ResponseMessage> responseMessages = wrapImmutable(Stream.concat(properties.getCodes().stream()
            .filter(it -> it.isEnum() && StatusCode.class.isAssignableFrom(it))
            .map(it -> (StatusCode[]) it.getEnumConstants())
            .flatMap(Arrays::stream), Arrays.stream(MessageCode.values()))
            .map(it -> new ResponseMessageBuilder().code(it.getCode()).message(it.getMessage()).build())
            .collect(Collectors.toList()));

        // 没有分组
        if (properties.getGroups().isEmpty()) {
            ApiInfo apiInfo = new ApiInfoBuilder()
                .title(properties.getTitle())
                .description(properties.getDescription())
                .version(properties.getVersion())
                .license(properties.getLicense())
                .licenseUrl(properties.getLicenseUrl())
                .contact(new Contact(properties.getContact().getName(),
                    properties.getContact().getUrl(),
                    properties.getContact().getEmail()))
                .termsOfServiceUrl(properties.getTermsOfServiceUrl())
                .build();

            Docket docket = buildDocket("default", properties.getBasePath(), properties.getExcludePath(),
                properties.getHost(), apiInfo, properties.getBasePackage(), responseMessages, properties.getParameters());

            beanFactory.registerSingleton("defaultDocket", docket);
        }

        // 分组创建
        for (Map.Entry<String, SwaggerProperties.GroupInfo> group : properties.getGroups().entrySet()) {
            SwaggerProperties.GroupInfo groupInfo = group.getValue();
            SwaggerProperties.Contact contact = groupInfo.getContact();
            SwaggerProperties.Contact defaultContact = properties.getContact();
            ApiInfo apiInfo = new ApiInfoBuilder()
                .title(defaultString(groupInfo.getTitle(), properties.getTitle()))
                .description(defaultString(groupInfo.getDescription(), properties.getDescription()))
                .version(defaultString(groupInfo.getVersion(), properties.getVersion()))
                .license(defaultString(groupInfo.getLicense(), properties.getLicense()))
                .licenseUrl(defaultString(groupInfo.getLicenseUrl(), properties.getLicenseUrl()))
                .contact(new Contact(defaultString(contact.getName(), defaultContact.getName()),
                    defaultString(contact.getUrl(), defaultContact.getUrl()),
                    defaultString(contact.getEmail(), defaultContact.getEmail())))
                .termsOfServiceUrl(defaultString(groupInfo.getTermsOfServiceUrl(), properties.getTermsOfServiceUrl()))
                .build();

            Docket docket = buildDocket(group.getKey(), groupInfo.getBasePath(), groupInfo.getExcludePath(),
                properties.getHost(), apiInfo, groupInfo.getBasePackage(), responseMessages, properties.getParameters());

            beanFactory.registerSingleton(group.getKey() + "Docket", docket);
        }
    }

    private static Docket buildDocket(String groupName, List<String> basePaths, List<String> excludePaths, String host, ApiInfo apiInfo, String basePackage, List<ResponseMessage> responseMessages, List<SwaggerProperties.@Valid GlobalParameter> parameters) {
        Predicate<String> basePath = Predicates.or(buildBasePath(basePaths)
            .stream()
            .map(PathSelectors::ant)
            .collect(Collectors.toList()));

        Predicate<String> excludePath = Predicates.or(excludePaths
            .stream()
            .map(PathSelectors::ant)
            .collect(Collectors.toList()));

        return new Docket(DocumentationType.SWAGGER_2)
            .host(host)
            .groupName(groupName)
            .apiInfo(apiInfo)
            .globalOperationParameters(getGlobalOperationParameters(parameters))
            .globalResponseMessage(RequestMethod.GET, responseMessages)
            .globalResponseMessage(RequestMethod.POST, responseMessages)
            .select()
            .apis(RequestHandlerSelectors.basePackage(basePackage))
            .paths(Predicates.and(Predicates.not(excludePath), basePath))
            .build();
    }

    private static List<String> buildBasePath(List<String> basePaths) {
        // base-path处理
        // 当没有配置任何path的时候，解析/**
        return basePaths.isEmpty() ? Collections.singletonList(ALL_MATCH_PATH) : basePaths;
    }

    private static List<ResponseMessage> wrapImmutable(List<ResponseMessage> messages) {
        return Collections.unmodifiableList(messages);
    }

    private static String defaultString(@Nullable String value, String defaultValue) {
        return value == null || value.isEmpty() ? defaultValue : value;
    }
    
    /**  
     * 配置全局参数
     *
     */
    private static List<Parameter> getGlobalOperationParameters(List<SwaggerProperties.@Valid GlobalParameter> parameters) {
        List<Parameter> pars = new ArrayList<>();
        // 从Apollo读取配置
        ParameterBuilder parameterBuilder = new ParameterBuilder();
        for (SwaggerProperties.GlobalParameter parameter : parameters) {
        	 parameterBuilder.name(parameter.getName()).description(parameter.getDescription()).modelRef(new ModelRef(parameter.getModelRef())).parameterType(parameter.getParameterType()).required(false);
             pars.add(parameterBuilder.build());
		}
        return pars;
    }
}
